﻿//////////////////////////////////////////////////////////////////
//
// Icosahedron.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;

namespace Geometry
{
   public class Icosahedron
   {
      ////////////////////////////////////////////////////////////////////////
      //
      public Icosahedron(float side)
      {
         sideLength_ = side;
       
         a = new Point3D(0, yOrdOfA, 0);
         b = new Point3D(fromCentreToVertexOfPentagon, yOrdOfBtoF, 0);
         c = new Point3D(xOrdOfPointC, yOrdOfBtoF, zOrdOfPointC);
         d = new Point3D(-fromCentreToEdgeOfPentagon, yOrdOfBtoF, sideLength / 2.0f);
         e = new Point3D(-fromCentreToEdgeOfPentagon, yOrdOfBtoF, -sideLength / 2.0f);
         f = new Point3D(xOrdOfPointC, yOrdOfBtoF, -zOrdOfPointC);
         g = new Point3D(-fromCentreToVertexOfPentagon, -yOrdOfBtoF, 0);
         h = new Point3D(-xOrdOfPointC, -yOrdOfBtoF, -zOrdOfPointC);
         i = new Point3D(fromCentreToEdgeOfPentagon, -yOrdOfBtoF, -sideLength / 2.0f);
         j = new Point3D(fromCentreToEdgeOfPentagon, -yOrdOfBtoF, sideLength / 2.0f);
         k = new Point3D(-xOrdOfPointC, -yOrdOfBtoF, zOrdOfPointC);
         l = new Point3D(0, -yOrdOfA, 0);
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public float sideLength
      {
         get { return sideLength_; }
      }

      ////////////////////////////////////////////////////////////////////////
      // Individual vertices
      public Point3D a;
      public Point3D b;
      public Point3D c;
      public Point3D d;
      public Point3D e;
      public Point3D f;
      public Point3D g;
      public Point3D h;
      public Point3D i;
      public Point3D j;
      public Point3D k;
      public Point3D l;

      ////////////////////////////////////////////////////////////////////////
      // Intermediates
      //
      // The term "Pyramid" here refers to the two five-sided pyramids made
      // up from the equilateral triangles on the "top" and (upside-down) the
      // "bottom" of the icosahedron
      private float angle1 { get { return 0.4f * (float)Math.PI; } }
      private float angle2 { get { return 0.1f * (float)Math.PI; } }
      private float angle3 { get { return -0.3f * (float)Math.PI; } }
      private float angle4 { get { return 0.2f * (float)Math.PI; } }
      private float fromCentreToEdgeOfPentagon { get { return fromCentreToVertexOfPentagon * (float)Math.Cos(angle4); } }
      private float zOrdOfPointC { get { return fromCentreToVertexOfPentagon * (float)Math.Cos(angle2); } }
      private float xOrdOfPointC { get { return fromCentreToVertexOfPentagon * (float)Math.Sin(angle2); } }
      private float fromCentreToVertexOfPentagon { get { return sideLength / (2.0f * (float)Math.Sin(angle4)); } }
      private float heightOfFiveSidedPyramid { get { return (float)Math.Sqrt(sideLength * sideLength - fromCentreToVertexOfPentagon * fromCentreToVertexOfPentagon); } }
      private float fromTopToLowerPyramidBase { get { return (float)Math.Sqrt((fromCentreToEdgeOfPentagon + fromCentreToVertexOfPentagon) * (fromCentreToEdgeOfPentagon + fromCentreToVertexOfPentagon) - fromCentreToEdgeOfPentagon * fromCentreToEdgeOfPentagon); } }
      private float yOrdOfA { get { return yOrdOfBtoF + heightOfFiveSidedPyramid; } }
      private float yOrdOfBtoF { get { return 0.5f * (fromTopToLowerPyramidBase - heightOfFiveSidedPyramid); } }

      ////////////////////////////////////////////////////////////////////////

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Triangles that go up to make an icosahedron
      /// </summary>
      public Triangle[] triangles
      {
         get
         {
            // By default, triangle vertices are listed counter-clockwise looking
            // out from the centre of the icosahedron to match the OpenGL
            // standard. Set clockwise to true to do them clockwise, as per
            // Direct3D
            if (clockwise)
            {
               return new Triangle[]
               {
                    new Triangle(a, b, c)  // #0
                  , new Triangle(a, f, b)  // #1
                  , new Triangle(a, c, d)  // #2
                  , new Triangle(a, d, e)  // #3
                  , new Triangle(a, e, f)  // #4
                  , new Triangle(b, j, c)  // #5
                  , new Triangle(b, f, i)  // #6
                  , new Triangle(b, i, j)  // #7
                  , new Triangle(c, k, d)  // #8
                  , new Triangle(c, j, k)  // #9
                  , new Triangle(d, g, e)  // #10
                  , new Triangle(d, k, g)  // #11
                  , new Triangle(e, h, f)  // #12            
                  , new Triangle(e, g, h)  // #13
                  , new Triangle(f, h, i)  // #14
                  , new Triangle(g, l, h)  // #15
                  , new Triangle(g, k, l)  // #16
                  , new Triangle(h, l, i)  // #17
                  , new Triangle(i, l, j)  // #18
                  , new Triangle(j, l, k)  // #19
            };
            }
            return new Triangle[]
            {
                 new Triangle(a, c, b)  // #0
               , new Triangle(a, b, f)  // #1
               , new Triangle(a, d, c)  // #2
               , new Triangle(a, e, d)  // #3
               , new Triangle(a, f, e)  // #4
               , new Triangle(b, c, j)  // #5
               , new Triangle(b, i, f)  // #6
               , new Triangle(b, j, i)  // #7
               , new Triangle(c, d, k)  // #8
               , new Triangle(c, k, j)  // #9
               , new Triangle(d, e, g)  // #10
               , new Triangle(d, g, k)  // #11
               , new Triangle(e, f, h)  // #12            
               , new Triangle(e, h, g)  // #13
               , new Triangle(f, i, h)  // #14
               , new Triangle(g, h, l)  // #15
               , new Triangle(g, l, k)  // #16
               , new Triangle(h, i, l)  // #17
               , new Triangle(i, j, l)  // #18
               , new Triangle(j, k, l)  // #19
         };
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// The Cartesian coordinates for an icosahedron, given the length of one
      /// of its sides
      /// </summary>
      public Point3D[] vertices
      {
         get
         {
            return new Point3D[] { a, b, c, d, e, f, g, h, i, j, k, l };
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      private float sideLength_;
      public static bool clockwise = false;
   }
}
